﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Properties;

namespace Borg
{
    public class ExtensionInfo : RepositoryElement
    {
        internal static int nextId = 1;

        private bool _isReleased;

        internal readonly ExtensionTypeInfo extensionTypeInfo;
        internal RepositoryCollection<PlugInfo> plugInfos;
        internal RepositoryCollection<SlotInfo> slotInfos;

        internal SlotInfo owner;
        internal object extensionObject;
        internal bool autoRelease;

        internal event ExtensionEventHandler InternalCreated;
        public event CancelExtensionEventHandler Releasing;
        public event ExtensionEventHandler Released;

        public virtual ExtensionTypeInfo ExtensionTypeInfo
        {
            get { return extensionTypeInfo; }
        }

        public virtual RepositoryCollection<PlugInfo> PlugInfos
        {
            get { return plugInfos; }
        }

        public virtual RepositoryCollection<SlotInfo> SlotInfos
        {
            get { return slotInfos; }
        }

        public override string Name
        {
            get { return extensionTypeInfo.Name; }
        }

        public override Assembly Assembly
        {
            get { return extensionTypeInfo.pluginInfo.assembly; }
        }

        public object Object
        {
            get { return extensionObject; }
        }

        public bool AutoRelease
        {
            get { return autoRelease; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException(Messages.CompositionNotAllowed);

                Runtime.Composer.SetAutoRelease(this, value);
            }
        }

        public SlotInfo Owner
        {
            get { return owner; }
        }

        public bool IsPlugged
        {
            get { return plugInfos.Any(plugInfo => plugInfo.pluggedInSlots.Count > 0); }
        }

        public bool IsReleased
        {
            get { return _isReleased; }
        }

        public bool IsShared
        {
            get { return extensionTypeInfo.GetSharedExtension(false) == this; }
        }

        static ExtensionInfo()
        { }

        protected internal ExtensionInfo(ExtensionTypeInfo extensionTypeInfo, object extensionObject)
        {
            id = nextId++;

            this.extensionTypeInfo = extensionTypeInfo;
            this.extensionObject = extensionObject;
            autoRelease = extensionTypeInfo.extensionAttr.AutoRelease;

            var sis = extensionTypeInfo.slotTypeInfos.
                Select(slotTypeInfo => new SlotInfo(slotTypeInfo, this)).ToList();

            slotInfos = new RepositoryCollection<SlotInfo>(sis);

            IList<PlugInfo> pis = extensionTypeInfo.plugTypeInfos.
                Select(plugTypeInfo => new PlugInfo(plugTypeInfo, this)).ToList();

            plugInfos = new RepositoryCollection<PlugInfo>(pis);

            if (!extensionTypeInfo.eventHandlerMethodInfosSet)
                return;

            if (extensionTypeInfo.MiOnCreated != null)
                InternalCreated +=
                    new ReflectionEventHandler(extensionObject, extensionTypeInfo.MiOnCreated).InvokeMethod;

            if (extensionTypeInfo.MiOnReleasing != null)
                Releasing +=
                    new ReflectionEventHandler(extensionObject, extensionTypeInfo.MiOnReleasing).InvokeMethod;

            if (extensionTypeInfo.MiOnReleased != null)
                Released +=
                    new ReflectionEventHandler(extensionObject, extensionTypeInfo.MiOnReleased).InvokeMethod;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void OpenSlots()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.OpenSlots(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void CloseSlots()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.CloseSlots(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void RegisterInSlots()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.RegisterInSlots(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void RegisterInSlots(ExtensionTypeInfo eti)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.RegisterInSlots(this, eti);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void DeregisterFromSlots()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.DeregisterFromSlots(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void DeregisterFromSlots(ExtensionTypeInfo eti)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.DeregisterFromSlots(this, eti);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void PlugPlugs()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.PlugPlugs(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void PlugPlugs(ExtensionInfo ei)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.PlugPlugs(this, ei);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void PlugInSlots()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.PlugInSlots(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void PlugInSlots(ExtensionInfo ei)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.PlugInSlots(this, ei);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void PlugInSlots(ExtensionTypeInfo eti)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.PlugInSlots(this, eti);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void UnplugPlugs()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.UnplugPlugs(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void UnplugPlugs(ExtensionInfo ei)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.UnplugPlugs(this, ei);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void UnplugFromSlots()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.UnplugFromSlots(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void UnplugFromSlots(ExtensionInfo ei)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.UnplugFromSlots(this, ei);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Release()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException(Messages.CompositionNotAllowed);

            Runtime.Composer.Release(this);
        }

        public void LogEvent(LogEventArgs args)
        {
            Runtime.OnLogEvent(this, args);
        }

        public void LogEvent(string msg)
        {
            LogEvent(new LogEventArgs(msg));
        }

        public void LogEvent(string message, params object[] args)
        {
            LogEvent(new LogEventArgs(MessageType.Info, VerbosityLevel.Minimal, message, args));
        }

        public void LogEvent(MessageType type, string message, params object[] args)
        {
            LogEvent(new LogEventArgs(type, VerbosityLevel.Minimal, message, args));
        }

        internal void InternalOpenSlots(bool isManual, uint taskGroupId, Assembly initiator)
        {
            if (!isManual && !Runtime.AutoOpen)
                return;

            var status = false;

            foreach (var slotInfo in slotInfos)
            {
                if (slotInfo.InternalOpen(isManual, taskGroupId, initiator, false))
                    status = true;
            }

            if (!status)
                return;

            Runtime.TaskQueue.Enqueue(
                TaskFactory.CreateRegisterInSlots(
                    this,
                    false,
                    taskGroupId,
                    Runtime.Assembly));
        }

        internal void InternalCloseSlots(Assembly initiator, bool force)
        {
            foreach (var slotInfo in slotInfos)
                slotInfo.InternalClose(initiator, force);
        }

        internal void InternalRegisterInSlots(bool isManual, uint taskGroupId, Assembly initiator)
        {
            foreach (var eti in Runtime.Repository.ExtensionTypeInfos.Where(eti => extensionTypeInfo.HasSlotFor(eti)))
                InternalRegisterInSlots(eti, isManual, taskGroupId, initiator);
        }

        internal void InternalRegisterInSlots(ExtensionTypeInfo eti, bool isManual, uint taskGroupId, Assembly initiator)
        {
            if (eti == null)
                return;

            var status = false;

            foreach (var slotInfo in slotInfos)
            {
                var pti = eti.plugTypeInfos[slotInfo.Name];

                if (pti != null && slotInfo.InternalRegister(pti, isManual, taskGroupId, initiator, false))
                    status = true;
            }

            if (!status)
                return;

            Runtime.TaskQueue.Enqueue(
                TaskFactory.CreatePlugInSlots(
                    this,
                    eti,
                    false,
                    taskGroupId,
                    Runtime.Assembly));
        }

        internal void InternalDeregisterFromSlots(Assembly initiator, bool force)
        {
            foreach (var slotInfo in slotInfos)
            {
                while (slotInfo.registeredPlugTypeInfos.Count > 0)
                    InternalDeregisterFromSlots(
                        slotInfo.registeredPlugTypeInfos[0].extensionTypeInfo, initiator, force);
            }
        }

        internal void InternalDeregisterFromSlots(ExtensionTypeInfo eti, Assembly initiator, bool force)
        {
            if (eti == null)
                return;

            foreach (var slotInfo in slotInfos)
            {
                var pti = eti.plugTypeInfos[slotInfo.Name];

                if (pti != null)
                    slotInfo.InternalDeregister(pti, initiator, force);
            }
        }

        internal void InternalPlugPlugs(bool isManual, uint taskGroupId, Assembly initiator, ExtensionInfo ignoreExt)
        {
            if (!isManual && !Runtime.AutoPlug)
                return;

            bool status = false;

            foreach (var plugInfo in plugInfos)
                foreach (var slotInfo in plugInfo.plugTypeInfo.registeredInSlots)
                    if (slotInfo.extensionInfo != ignoreExt &&
                        !slotInfo.pluggedPlugInfos.Contains(plugInfo) &&
                        slotInfo.extensionInfo.InternalPlugInSlots(this, isManual, taskGroupId, initiator))
                        status = true;

            if (status)
                Runtime.TaskQueue.Enqueue(
                    TaskFactory.CreateOpenSlots(
                        this,
                        false,
                        taskGroupId,
                        Runtime.Assembly));
        }

        internal void InternalPlugInSlots(uint taskGroupId, Assembly initiator)
        {
            foreach (var slotInfo in slotInfos)
                foreach (var pti in slotInfo.registeredPlugTypeInfos)
                    slotInfo.InternalPlug(pti, true, taskGroupId, initiator);
        }

        internal bool InternalPlugInSlots(ExtensionTypeInfo eti, bool isManual, uint taskGroupId, Assembly initiator)
        {
            if (eti == null)
                return false;

            var status = false;

            foreach (var plugTypeInfo in eti.plugTypeInfos)
            {
                var slotInfo = slotInfos[plugTypeInfo.Name];

                if (slotInfo == null || !slotInfo.registeredPlugTypeInfos.Contains(plugTypeInfo) ||
                    !isManual && (!Runtime.AutoPlug || !slotInfo.autoPlug))
                    continue;

                ExtensionInfo ei;

                if (!slotInfo.unique)
                {
                    if (eti.sharedExtension != null)
                    {
                        if (!slotInfo.pluggedPlugInfos.Contains(eti.sharedExtension.plugInfos[slotInfo.Name]))
                            ei = eti.sharedExtension;
                        else
                            continue;
                    }
                    else if (isManual || !Runtime.LazyLoad && !slotInfo.lazyLoad && plugTypeInfo.AutoPlug)
                        ei = eti.InternalCreateSharedExtension(
                            null,
                            AppDomain.CurrentDomain,
                            isManual,
                            taskGroupId,
                            Runtime.Assembly,
                            this);
                    else
                        continue;
                }
                else if (isManual || !Runtime.LazyLoad && !slotInfo.lazyLoad && plugTypeInfo.AutoPlug)
                {
                    foreach (var plugInfo in slotInfo.pluggedPlugInfos)
                        if (plugInfo.plugTypeInfo != plugTypeInfo)
                        { }

                    ei = eti.InternalCreateExtension(
                        isManual,
                        false,
                        AppDomain.CurrentDomain,
                        Runtime.Assembly);
                }
                else
                    continue;

                if (InternalPlugInSlots(ei, isManual, taskGroupId, initiator))
                    status = true;
            }

            return status;
        }

        internal bool InternalPlugInSlots(ExtensionInfo ei, bool isManual, uint taskGroupId, Assembly initiator)
        {
            if (ei == null)
                return false;

            var status = false;

            foreach (var pi in ei.plugInfos)
            {
                var slotInfo = slotInfos[pi.Name];

                if (slotInfo != null &&
                    slotInfo.registeredPlugTypeInfos.Contains(pi.plugTypeInfo) &&
                    !slotInfo.pluggedPlugInfos.Contains(pi) &&
                    slotInfo.InternalPlug(pi, isManual, taskGroupId, initiator, false))
                    status = true;
            }

            Runtime.TaskQueue.Enqueue(
                TaskFactory.CreateOpenSlots(
                    ei,
                    false,
                    taskGroupId,
                    Runtime.Assembly));

            return status;
        }

        internal void InternalUnplugPlugs(Assembly initiator, bool force)
        {
            foreach (var plugInfo in plugInfos)
                for (var i = plugInfo.pluggedInSlots.Count - 1; i >= 0; --i)
                    InternalUnplugPlugs(plugInfo.pluggedInSlots[i].extensionInfo, initiator, force);
        }

        internal void InternalUnplugPlugs(ExtensionInfo ei, Assembly initiator, bool force)
        {
            if (ei == null)
                return;

            foreach (var pi in plugInfos)
            {
                var slotInfo = ei.slotInfos[pi.Name];

                if (slotInfo != null)
                    slotInfo.InternalUnplug(pi, initiator, force);
            }
        }

        internal void InternalUnplugFromSlots(Assembly initiator, bool force)
        {
            foreach (var slotInfo in slotInfos)
                for (var i = slotInfo.pluggedPlugInfos.Count - 1; i >= 0; --i)
                    InternalUnplugFromSlots(slotInfo.extensionInfo, initiator, force);
        }

        internal void InternalUnplugFromSlots(ExtensionInfo ei, Assembly initiator, bool force)
        {
            if (ei == null)
                return;

            foreach (var slotInfo in slotInfos)
            {
                var pi = ei.plugInfos[slotInfo.Name];

                if (pi != null)
                    slotInfo.InternalUnplug(pi, initiator, force);
            }
        }

        internal void InternalRelease(Assembly initiator, bool force)
        {
            if (_isReleased)
                return;

            var args = new CancelExtensionEventArgs(this, initiator);
            OnReleasing(extensionTypeInfo, args);

            if (!force && args.Cancel)
                Runtime.OnLogEvent(
                    this,
                    new LogEventArgs(
                        MessageType.Info,
                        VerbosityLevel.Normal,
                        Messages.ReleasingExtensionCanceled,
                        new object[]
                            {
                                id.ToString(CultureInfo.InvariantCulture),
                                Name
                            }));
            else
            {
                _isReleased = true;
                InternalCloseSlots(Runtime.assembly, true);

                foreach (var plugInfo in plugInfos)
                    plugInfo.InternalUnplug(Runtime.assembly, true);

                extensionTypeInfo.extensionInfos.Remove(this);
                Runtime.Repository.objects.Remove(extensionObject);

                if (extensionTypeInfo.sharedExtension == this)
                    extensionTypeInfo.sharedExtension = null;

                foreach (var monitorInfo in extensionTypeInfo.monitorInfos)
                    monitorInfo.Release();

                OnReleased(extensionTypeInfo, new ExtensionEventArgs(this, initiator));

                var disposable = extensionObject as IDisposable;

                if (disposable != null)
                    disposable.Dispose();

                extensionObject = null;
            }
        }

        internal void OnCreated(object sender, ExtensionEventArgs args)
        {
            Runtime.OnLogEvent(
                sender,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Diagnostic,
                    Messages.ExtensionCreated,
                    new object[]
                        {
                            id.ToString(CultureInfo.InvariantCulture),
                            Name
                        }));

            if (!extensionTypeInfo.eventHandlerMethodInfosSet)
                ExtensionTypeInfo.VerifyEventHandlerMethods(
                    (extensionTypeInfo.qualificationState &
                     QualificationState.MethodQualificationMissing) ==
                    QualificationState.MethodQualificationMissing);

            if (InternalCreated != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.ExtensionEventHandlerMethodDesc,
                        "Created",
                        Name),
                    InternalCreated,
                    sender,
                    args);

            extensionTypeInfo.OnExtensionCreated(sender, args);
        }

        internal void OnReleasing(object sender, CancelExtensionEventArgs args)
        {
            Runtime.OnLogEvent(
                sender,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Diagnostic,
                    Messages.ExtensionReleasing,
                    new object[]
                        {
                            id.ToString(CultureInfo.InvariantCulture),
                            Name
                        }));

            if (Releasing != null)
                Runtime.InvokeCancelEventHandler(
                    string.Format(
                        Messages.ExtensionEventHandlerMethodDesc,
                        "Releasing",
                        Name),
                    Releasing,
                    sender,
                    args);

            extensionTypeInfo.OnExtensionReleasing(sender, args);
        }

        internal void OnReleased(object sender, ExtensionEventArgs args)
        {
            Runtime.OnLogEvent(
                sender,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Diagnostic,
                    Messages.ExtensionReleased,
                    new object[]
                        {
                            id.ToString(CultureInfo.InvariantCulture),
                            Name
                        }));

            if (Released != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.ExtensionEventHandlerMethodDesc,
                        "Released",
                        Name),
                    Released,
                    sender,
                    args);

            extensionTypeInfo.OnExtensionReleased(sender, args);
        }
    }
}

